package com.xzy.demo.spike;

import com.xzy.demo.common.msg.Message;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author xzy
 * @date 2021/8/22 20:03
 * 说明：商品秒杀
 */
@Slf4j
@Service
public class SpikeServiceImpl implements SpikeService {
    /**
     * 秒杀商品key前缀
     */
    private final String SPIKE_COMMODITY_PREFIX = "spike.commodity:";
    /**
     * 秒杀顾客key前缀
     */
    private final String SPIKE_CUSTOMER_PREFIX = "spike.customer:";

    private final RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public SpikeServiceImpl(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 初始化秒杀商品信息
     *
     * @param commodityId - 商品ID
     * @param quantity    - 秒杀数量
     * @return - 操作结果
     */
    @Override
    public String initialize(Long commodityId, Long quantity) {
        // 重置商品数量
        redisTemplate.opsForValue().set(SPIKE_COMMODITY_PREFIX + commodityId, quantity);

        // 重置秒杀顾客
        redisTemplate.delete(SPIKE_CUSTOMER_PREFIX + commodityId);

        return "初始化完成";
    }

    /**
     * 查看秒杀商品当前信息
     *
     * @param commodityId - 商品ID
     * @return - 秒杀商品当前信息
     */
    @Override
    public String info(Long commodityId) {
        Integer remainingQuantity = (Integer) redisTemplate.opsForValue().get(SPIKE_COMMODITY_PREFIX + commodityId);
        Set<Object> customers = redisTemplate.opsForSet().members(SPIKE_CUSTOMER_PREFIX + commodityId);
        return "剩余数量：" + remainingQuantity + " 秒杀顾客：" + customers;
    }

    /**
     * 商品秒杀：存在商品超卖问题
     *
     * @param commodityId - 商品ID
     * @param customerId  - 顾客ID
     * @return - 秒杀结果
     */
    @Override
    public Message spick1(Long commodityId, Long customerId) {

        // 即使使用JVM锁对下列操作进行并发控制，也仅能保证单个程序的并发

        // 1.检查商品数量
        Integer remainingQuantity = (Integer) redisTemplate.opsForValue().get(SPIKE_COMMODITY_PREFIX + commodityId);
        if (remainingQuantity == null || remainingQuantity < 1) {
            return Message.fail("秒杀失败！");
        }

        // 2.减少商品数量
        redisTemplate.opsForValue().decrement(SPIKE_COMMODITY_PREFIX + commodityId);

        // 3.记录秒杀顾客
        redisTemplate.opsForSet().add(SPIKE_CUSTOMER_PREFIX + commodityId, customerId);

        return Message.ok("秒杀成功！");
    }

    /**
     * 商品秒杀：解决商品超卖问题，但存在库存遗留问题
     *
     * @param commodityId - 商品ID
     * @param customerId  - 顾客ID
     * @return - 秒杀结果
     */
    @Override
    public Message spick2(Long commodityId, Long customerId) {

        List<Object> result = redisTemplate.execute(new SessionCallback<List<Object>>() {
            @Override
            public <K, V> List<Object> execute(RedisOperations<K, V> operations) throws DataAccessException {
                // 乐观锁：执行exec操作前，如果监视的key被修改，取消exec操作
                redisTemplate.watch(SPIKE_COMMODITY_PREFIX + commodityId);

                // 1.检查商品数量
                Integer remainingQuantity = (Integer) redisTemplate.opsForValue().get(SPIKE_COMMODITY_PREFIX + commodityId);
                if (remainingQuantity == null || remainingQuantity < 1) {
                    return null;
                }

                // 事务管理
                operations.multi();

                // 2.减少商品数量
                redisTemplate.opsForValue().decrement(SPIKE_COMMODITY_PREFIX + commodityId);

                // 3.记录秒杀顾客
                redisTemplate.opsForSet().add(SPIKE_CUSTOMER_PREFIX + commodityId, customerId);

                return operations.exec();
            }
        });


        return (result == null || result.isEmpty()) ? Message.fail("秒杀失败！") : Message.ok("秒杀成功！");
    }

    /**
     * 商品秒杀：使用Lua脚本解决库存遗留问题
     *
     * @param commodityId - 商品ID
     * @param customerId  - 顾客ID
     * @return - 秒杀结果
     */
    @Override
    public Message spick3(Long commodityId, Long customerId) {
        // 准备Lua脚本
        DefaultRedisScript<Long> luaScript = new DefaultRedisScript<>();
        luaScript.setResultType(Long.class);
        luaScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("spike.lua")));

        // 准备参数
        List<String> keys = new ArrayList<>(2);
        keys.add(commodityId.toString());
        keys.add(customerId.toString());

        // 执行Lua脚本
        Long result = redisTemplate.execute(luaScript, keys, Collections.EMPTY_LIST);

        return (result == null || result.compareTo(0L) == 0) ? Message.ok("秒杀成功！") : Message.fail("秒杀失败！");
    }
}
